/*\
gcc -O2 -o captureaudio captureaudio.c -lasound
\*/

#include <stdio.h>
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

#define SAMPLERATE (48000)	/* audio dongle only does 44100 & 48000.. */
int sample_rate = SAMPLERATE;
int blockspersec = 4;
int kerchunklimit = 0;	  /* morse simplex repeater = 0? */
int squelchtimeout = 300;
int unsquelchtimeout = 30;	/* morse simplex repeater will need ~5s */
char audiodevice[80] = "hw:CARD=Set,DEV=0"; //"plughw:CARD=CODEC,DEV=0";
//char audiodevice[80] = "hw:CARD=Audio,DEV=0"; //"plughw:CARD=CODEC,DEV=0";

#pragma pack(push, 1) /* exact fit - align at byte-boundary, no padding */
typedef struct wav_header
{
  char riff_header[4];       /* Contains "RIFF" */
  unsigned int wav_size;     /* Size of the wav portion of the file */
  char wave_header[4];       /* Contains "WAVE" */
  char fmt_header[4];  /* Contains "fmt " (includes trailing space) */
  unsigned int fmt_chunk_size; /* Should be 16 for PCM */
  unsigned short audio_format; /* Should be 1 for PCM. 3 for IEEE Float */
  unsigned short num_channels;
  unsigned int sample_rate;
  unsigned int byte_rate; /* sample_rate * num_channels * Bytes Per Sample */
  unsigned short sample_alignment; /* num_channels * Bytes Per Sample */
  unsigned short bit_depth;        /* Number of bits per sample */
  char data_header[4];             /* Contains "data" */
  unsigned int data_bytes; /* samples * num_channels * sample byte size */
} wav_header;
#pragma pack(pop)


/* ---------------------------------------------------------------------- */

void abandon_ship (char* str, const char* arg)
{
  fprintf(stderr, str, arg);
  exit(1);
}


int main (int argc, char*argv[])
{
  int rc;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
#define MAXREC 10000
  short* recording[MAXREC];

  //

  argv++;
  while (argc > 1 && **argv == '-')
    {
      switch ((*argv)[1])
        {
        case 't':               /* timeout when no received morse at all */
          argv++; argc--; squelchtimeout = strtof (argv[0], 0); break;

        case 's':               /* when something received */
          argv++; argc--; unsquelchtimeout = strtof (argv[0], 0); break;

        case 'b':
          argv++; argc--; blockspersec = strtof (argv[0], 0); break;

        case 'k':
          argv++; argc--; kerchunklimit = strtof (argv[0], 0); break;

        case 'f':
          argv++; argc--; sample_rate = strtof (argv[0], 0); break;

	case 'd':
	  argv++; argc--; strncpy (audiodevice, argv[0], 80); break;

        default:
          fprintf (stderr, "Usage: captureaudio [-t no-input-timeout(sec)] "
		   "[-s unsquelched_timeout(sec)] [-f sample_rate(Hz)] "
		   "[-k kerchunklimit(sec)] "
		   "[-b block size] [-d alsa_audio_device");
          return 0;
        }
      argv++; argc--;
    }

  int sample_avg = sample_rate / blockspersec;

  //

  rc = snd_pcm_open (&handle, audiodevice, SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) abandon_ship ("unable to open pcm device: %s\n", snd_strerror(rc));

  snd_pcm_hw_params_alloca (&params); /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_any (handle, params); /* Fill it in with default values. */
  snd_pcm_hw_params_set_access (handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); /* Interleaved mode */
  snd_pcm_hw_params_set_format (handle, params, SND_PCM_FORMAT_S16_LE); /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_channels (handle, params, 1); /* mono */
  val = sample_rate;
  frames = sample_avg;
  snd_pcm_hw_params_set_rate_near (handle, params, &val, &dir);
  snd_pcm_hw_params_set_period_size_near (handle, params, &frames, &dir);
  rc = snd_pcm_hw_params (handle, params); /* Write the parameters to the driver */
  if (rc < 0) abandon_ship ("unable to set audio hardware parameters: %s\n", snd_strerror(rc));

  //

  double mytime = 0.0, lastunsqtime = 0.0;
  int firstunsq = 0, lastunsq = 0;
  double p, maxpow = 0.0;
  double avgpow = 0.0, sumnoisepow = 0.0;
  double sigpow = 0.0, sumsigpow = 0.0, thresh = 1300.0, sigavg = 0.0;
  int i, pass = 1, noisecnt = 0, sigcnt = 0;

  while (1)
    {
      recording[pass] = malloc(sizeof(short)*(int)frames);

      rc = snd_pcm_readi (handle, (char*)recording[pass], (int)frames);
      if (rc != (int)frames)
	fprintf (stderr, "short read, read %d frames\n", rc);

      //

      for (p = 0, i = 0; i < frames; i++)
	{
	  short a = recording[pass][i];
	  p += (a > 0) ? a : -a;
	}
      p = p / frames;

      sumnoisepow += p;
      noisecnt++;
      avgpow = sumnoisepow / (double)pass;
      if ((avgpow > 400) && (pass < 10)) avgpow = 1000; /* channel in use at start, force an unsquelching */
      if (p > maxpow) maxpow = p;
      if (p > 10000) fprintf (stderr, "overlevel p=%f\n", p);

      mytime += (double)sample_avg / (double)sample_rate;
      //fprintf (stderr, "mytime=%f, avg=%f\n", mytime, avgpow);

      if ( !firstunsq && (mytime > squelchtimeout) )
	break;
      if ( firstunsq && ((mytime - lastunsqtime) > unsquelchtimeout))
	break;
      if (pass == MAXREC)
	break;

      //

      if (sigcnt)
	{
	  sigavg = sumsigpow / (double)sigcnt;
	  thresh = (sigavg + avgpow) / 2;
	}

      if ( (p > thresh) || (p > (2*avgpow)) )
	{
	  sumsigpow += p;
	  sigcnt++;
	  sumnoisepow -= p - avgpow;

	  if (!firstunsq) firstunsq = pass;
	  lastunsq = pass;
	  lastunsqtime = mytime;
	  fprintf (stderr, "unsqelched at %f, avg=%f, pow=%f, sigavg=%f\n", mytime, avgpow, p, sigavg);
	}

      pass++;
    }

  //

  fprintf (stderr, "end at %f, unsquelched=%d, ", mytime, firstunsq ? 1 : 0);
  fprintf (stderr, "avgpow=%f, sigavg=%f, maxpow=%f\n", avgpow, sigavg, maxpow);

  if (firstunsq)
    {
      firstunsq -= blockspersec;
      if (firstunsq < 1)  firstunsq = 1;
      lastunsq += blockspersec;
      if (lastunsq >= pass) lastunsq = pass-1;

      double dur = (1 + lastunsq - firstunsq) / (double)blockspersec;

      if (dur >= kerchunklimit)
	{
	  fprintf (stderr, "writing from %f to %f seconds\n",
		   (double)firstunsq/(double)blockspersec, (double)(lastunsq+1)/(double)blockspersec);

	  wav_header h;
	  h.riff_header[0] = 'R'; h.riff_header[1] = 'I'; h.riff_header[2] = 'F'; h.riff_header[3] = 'F';
	  h.wave_header[0] = 'W'; h.wave_header[1] = 'A'; h.wave_header[2] = 'V'; h.wave_header[3] = 'E';
	  h.fmt_header[0] = 'f'; h.fmt_header[1] = 'm'; h.fmt_header[2] = 't'; h.fmt_header[3] = ' ';
	  h.fmt_chunk_size = 16;
	  h.audio_format = 1; h.num_channels = 1; h.sample_alignment = 2; h.bit_depth = 16;
	  h.data_header[0] = 'd'; h.data_header[1] = 'a'; h.data_header[2] = 't'; h.data_header[3] = 'a';
	  h.wav_size = 36 + (sample_avg * 2 * (lastunsq - firstunsq + 1));
	  h.data_bytes = sample_avg * 2 * (lastunsq - firstunsq + 1);
	  h.sample_rate = sample_rate; h.byte_rate = sample_rate * 1 * 2;

	  fwrite (&h, 1, sizeof(h), stdout);

	  // perhaps normalization/compression could be done..
	  for (i = firstunsq; i <= lastunsq; i++)
	    fwrite (recording[i], sizeof(short), sample_avg, stdout);
	}
      else
	{
	  fprintf (stderr, "kerchunked first=%d last=%d dur=%f\n", firstunsq, lastunsq, dur);
	  firstunsq = 0;
	}
    }

  // freeing malloc'd memory at the end is silly..
  return !firstunsq;
}
